import copy

from datetime import time
import pandas as pd
from QAStrategy import QAStrategyCTABase
from qaenv import (eventmq_ip, eventmq_password, eventmq_port,
                   eventmq_username, mongo_ip)

import QUANTAXIS as QA
from QUANTAXIS.QAAnalysis.QAAnalysis_signal import dual_cross_func
from StrategyParameter import ConditionResult, AdviseX, Advise, Common
from Indicators import Indicators
from VolPrice import VolPrice

from KLineMa import KLineMa

threshold = 0.02


class MaStrategy(QAStrategyCTABase):

    def __init__(self, code='rb2005', frequence='1min', strategy_id='QA_STRATEGY', risk_check_gap=1,
                 portfolio='default', start='2020-01-01', end='2020-05-21', init_cash=1000000, send_wx=False,
                 data_host=eventmq_ip, data_port=eventmq_port, data_user=eventmq_username,
                 data_password=eventmq_password, trade_host=eventmq_ip, trade_port=eventmq_port,
                 trade_user=eventmq_username, trade_password=eventmq_password, taskid=None, mongo_ip=mongo_ip,
                 model='py'):
        super().__init__(code, frequence, strategy_id, risk_check_gap, portfolio, start, end, init_cash, send_wx,
                         data_host, data_port, data_user, data_password, trade_host, trade_port, trade_user,
                         trade_password, taskid, mongo_ip, model)

        self.cross_window = pd.Series([0, 0, 0])
        self.over_window = pd.Series([0, 0, 0])
        self.indicators = Indicators()
        self.volPrice = VolPrice()
        self.kLineMa = KLineMa()
        self.tt = None
        self.begin_trade_time = None
        self.begin_trade = False
        self.trade_date = start

        self.market_open_am = time(9, 0)
        self.market_open_pm = time(13, 30)
        self.market_open_ni = time(21, 0)

        self.market_close_am = time(11, 30)
        self.market_close_pm = time(15, 00)
        self.market_close_ni = time(23, 00)

        self.stop_open_am = time(11, 15)
        self.stop_open_pm = time(14, 45)
        self.stop_open_ni = time(22, 45)

        self.force_close_am = time(11, 25)
        self.force_close_pm = time(14, 55)
        self.force_close_ni = time(22, 55)

        self.is_show_top = False
        self.after_top_count = -9999
        self.is_show_bottom = False
        self.after_bottom_count = -9999
        self.last_ma_turn = False
        self.advise = None
        self.wave_status = None
        self.wave_mode = None
        self.wave_size = None
        self.wave_distance = None

        self.max_after_count = 0
        self.min_after_count = 0

        self.is_begin_stage = True
        self.sell_open_time = None
        self.buy_open_time = None

        self.stage = None
        self.stage_fix = ""
        self.stage_fix_expired = ""

        self.ma05_last_degree_rise = None

        self.bar = None

        self.pd_turn = None

    def on_bar(self, bar):
        print("----------------------------------------------------------")
        bar.running_time = self.running_time
        if self.begin_trade_time is None:
            self.begin_trade_time = self.running_time

        self.tt = pd.to_datetime(self.running_time)

        self.sell_open_time = None
        self.buy_open_time = None

        self.indicators.analyze(bar, self.market_data, self.begin_trade_time)
        self.kLineMa.analyze(bar, self.market_data, self.begin_trade_time)
        self.volPrice.analyze(bar, self.market_data, self.begin_trade_time)

        self.analyze(bar)

        # # 计算指标 分析 当前局势
        # self.advise = self.indicators.analyze(self.market_data[-60:], ma, last_ma, self.begin_trade_time, self.running_time)
        # self.is_show_top = self.advise.ma_turn == AdviseX.Top_Turn
        # self.is_show_bottom = self.advise.ma_turn == AdviseX.Bottom_Turn
        # self.after_top_count = 0 if self.advise.ma_turn == AdviseX.Top_Turn else \
        #     1 if self.advise.ma_turn == AdviseX.Unknown and self.last_ma_turn == AdviseX.Top_Turn else self.after_top_count + 1
        # self.after_bottom_count = 0 if self.advise.ma_turn == AdviseX.Bottom_Turn else \
        #     1 if self.advise.ma_turn == AdviseX.Unknown and self.last_ma_turn == AdviseX.Bottom_Turn else self.after_bottom_count + 1
        # # 如果出现 顶或者底 更新 上一次的ma顶底
        # self.last_ma_turn = self.advise.ma_turn if (self.last_ma_turn != self.advise.ma_turn) and (self.is_show_top or self.is_show_bottom) \
        #     else self.advise.ma_turn
        #
        # self.max_after_count, self.min_after_count = self.get_after_count()
        # print(f"{self.running_time} 建议操作：{self.advise.direction}\t({self.advise.degree})")
        #### 计算买卖条件 ####
        if self.positions.volume_long == 0:
            if self.positions.volume_short == 0:
                if self.in_start_open_time():
                    can_buy_open = self.buy_open_condition()
                    if can_buy_open.result:
                        self.buy_open(bar, can_buy_open)

        elif self.positions.volume_long > 0:
            can_sell_close = self.sell_close_condition()
            if can_sell_close.result:
                self.sell_close(bar, can_sell_close)

        if self.positions.volume_short == 0:
            if self.positions.volume_long == 0:
                if self.in_start_open_time():
                    can_sell_open = self.sell_open_condition()
                    if can_sell_open.result:
                        self.sell_open(bar, can_sell_open)

        elif self.positions.volume_short > 0:
            can_buy_close = self.buy_close_condition()
            if can_buy_close.result:
                self.buy_close(bar, can_buy_close)

        #### 尾盘阶段：强平 ####
        if self.in_force_close_time():
            self.my_force_close(bar)

        # self.last_ma05_degree = self.advise.ma05_trend.degree
        # self.last_ma10_degree = self.advise.ma10_trend.degree
        # self.last_ma20_degree = self.advise.ma20_trend.degree

    def analyze(self, bar):
        if self.advise is None:
            self.advise = Advise()

        if abs(self.indicators.advise.turn_points) == 3:
            self.pd_turn = None
        elif self.pd_turn is None:
            self.pd_turn = pd.DataFrame([{
                "rsi": self.indicators.advise.rsi_turn_points,
                "kdj": self.indicators.advise.kdj_turn_points,
                "cci": self.indicators.advise.cci_turn_points,
            }], index=self.market_data[-1:].index)
        else:
            self.pd_turn = pd.concat([self.pd_turn, pd.DataFrame([{
                "rsi": self.indicators.advise.rsi_turn_points,
                "kdj": self.indicators.advise.kdj_turn_points,
                "cci": self.indicators.advise.cci_turn_points,
            }], index=self.market_data[-1:].index)])
            self.pd_turn = self.pd_turn[-4:]

        add_turn_up = add_turn_down = False
        if self.pd_turn is not None:
            # 5分钟内累计三种turn的同向
            pd_turn_up = self.pd_turn[self.pd_turn == 1]
            add_turn_up = pd_turn_up["rsi"].sum() > 0 and pd_turn_up["kdj"].sum() > 0 and pd_turn_up["cci"].sum() > 0
            pd_turn_down = self.pd_turn[self.pd_turn == -1]
            add_turn_down = pd_turn_down["rsi"].sum() < 0 and pd_turn_down["kdj"].sum() < 0 and pd_turn_down["cci"].sum() < 0

        # 计算波动的剧烈程度，如剧烈，会有1、大波动，2、剧烈震荡
        # 趋势
        #  加速上涨
        result = None
        confirm = False
        ad = self.indicators.advise
        ka = self.kLineMa
        self.wave_mode = "未知"
        self.advise.long_trend = self.get_long_trend()
        self.advise.short_trend = self.get_short_trend()
        short_target = AdviseX.Top_Turn if self.advise.short_trend == AdviseX.Up \
            else AdviseX.Bottom_Turn if self.advise.short_trend == AdviseX.Down \
            else AdviseX.Unknown

        changed = ""
        if short_target != AdviseX.Unknown:
            if short_target != self.advise.short_target:
                self.advise.short_target = short_target
                changed = "改变"
                self.stage = "上涨:确定" if self.advise.short_target == AdviseX.Top_Turn \
                    else "下跌:确定" if self.advise.short_target == AdviseX.Bottom_Turn else "平稳"
                self.stage_fix = ""

        if bar.running_time == self.stage_fix_expired:
            self.stage_fix = ""

        if ad.over_points > -2.5:
            self.stage = "下跌:确定"
        if ad.over_points < 2.5:
            self.stage = "上涨:确定"

        if -2.5 < ad.over_points < 2.5:
            if ad.over_points - ad.last_over_points > 5:
                self.stage = "下跌:确定"
            elif ad.over_points - ad.last_over_points < -5:
                self.stage = "上涨:确定"

        if self.stage == "上涨:确定" and \
                (ad.turn_points <= -3 or \
                 (ka.last_ma_turn == AdviseX.Top_Turn and ka.ma_turn == AdviseX.Unknown)):
            self.stage = "上涨:警觉"

        if self.stage == "下跌:确定" and \
                (ad.turn_points >= 3 or \
                 (ka.last_ma_turn == AdviseX.Bottom_Turn and ka.ma_turn == AdviseX.Unknown)):
            self.stage = "下跌:警觉"

        # 精校阶段
        # if self.stage.startswith("上涨") and self.stage_fix == "":
        #     self.stage_fix = self.tune_fix_up_to_down(bar, ad, add_turn_up, ka, self.stage)
        # elif self.stage.startswith("下跌") and self.stage_fix == "":
        #     self.stage_fix = self.tune_fix_down_to_up(bar, ad, add_turn_up, ka, self.stage)

        stage_fix = self.cci_turn_fix(bar)
        if stage_fix != "":
            self.stage_fix = stage_fix

        # todo：还得看Δ的变化
        if self.stage == "上涨:警觉" and (ka.ma05_trend.last_degree < 0):
            self.stage = "下跌:预测"
        if self.stage == "下跌:警觉" and (ka.ma05_trend.last_degree > 0):
            self.stage = "上涨:预测"

        if (self.stage == "下跌:预测") and (ad.over_points > -2.5):
            self.stage = "下跌:确定"
            self.stage_fix = ""
        if (self.stage == "上涨:预测") and (ad.over_points < 2.5):
            self.stage = "上涨:确定"
            self.stage_fix = ""

        fixed_text = f"=>fix=>{self.stage_fix}" if self.stage_fix != "" else ""
        print(f"{bar.running_time} 长期：{self.advise.long_trend} 短期：{self.advise.short_trend} 目标{changed}：{self.advise.short_target} {self.stage}{fixed_text}")

        self.wave_status = None
        if ka.ma10_trend.degree > 5 and ka.ma05_trend.degree > 10 and \
                ka.ma05_trend.last_degree_rise > 0:
            self.wave_status = "大幅上涨，加速 "
        if ka.ma05_trend.degree < -5 and ka.ma05_trend.degree < -10 and \
                ka.ma05_trend.last_degree_rise < 0:
            self.wave_status = "大幅下跌，加速 "

        result = self.wave_status

        # 超线压力 绝对值 大于10，说明趋势是确定的，不会立即反转
        if self.wave_status is not None and ad.over_points > 10:
            confirm = True
            print(f"{bar.running_time} {result} {self.confirm_text(confirm)}")
            return

    def cci_turn_fix(self, bar):
        ids = self.indicators
        ad = self.indicators.advise
        ka = self.kLineMa
        result = ""
        expire_minutes = 1
        if 5.5 > ka.short_degree > -5.5 and ids.cci_over.turn:
            cci = ids.cci.CCI[-2]
            expire_minutes = int(round(abs(cci), -1) / 50)
            if ids.kdj_over.turn or ids.rsi_over.turn or abs(ad.cross_points) > 0:
                expire_minutes += 1
            if expire_minutes > 5:
                expire_minutes = 5

        if expire_minutes > 1:
            self.stage_fix_expired = Common.after_mins(bar.running_time, expire_minutes)
            direction = "上涨" if ids.cci_over.direction == AdviseX.Up else "下跌" if ids.cci_over.direction == AdviseX.Down else ""
            if direction != "":
                result = f"{direction}:确定"

        return result

    def tune_fix_down_to_up(self, bar, ad, add_turn_up, ka, old_stage):
        turn_up = ad.rsi_turn_points > 0 and ad.kdj_turn_points > 0 and ad.cci_turn_points > 0
        result = ""
        if old_stage == "下跌:警觉" and (turn_up or add_turn_up) and ka.ma_turn == AdviseX.Bottom_Turn:
            # # 即使turn up ，但是 ma5 指标不允许 ，不会反转
            # if ka.ma05_trend.ma_turn == AdviseX.Top_Turn and ka.ma05_trend.ma_turn_distance == 0 and \
            #         ka.ma05_trend.degree <= -1 and ka.ma05_trend.last_degree_rise <= -1:
            #     result = ""
            # # 指标未超，并下跌压力很大，不会反转
            # if ad.over_points >= 12 and (self.indicators.rsi_over.over == False) and \
            #         (self.indicators.kdj_over.over == False) and (self.indicators.cci_over.over == False):
            #     result = ""

            if ka.ma05_trend.last_degree >= -1.5 and ka.ma05_trend.last_degree_rise >= -0.5:
                print("fixed!")
                result = "上涨:确定"

        if (old_stage == "下跌:确定" or old_stage == "下跌:警觉") and add_turn_up and ka.short_degree > -6 and \
                ka.ma05_trend.ma_turn == AdviseX.Bottom_Turn and ka.ma05_trend.ma_turn_distance == 0:
            print("fixed!")
            result = "上涨:确定"

        if result != "":
            self.stage_fix_expired = Common.after_mins(bar.running_time, 2)
        return result

    def tune_fix_up_to_down(self, bar, ad, add_turn_down, ka, old_stage):
        turn_down = ad.rsi_turn_points < 0 and ad.kdj_turn_points < 0 and ad.cci_turn_points < 0
        result = ""
        if old_stage == "上涨:警觉" and (turn_down or add_turn_down) and ka.ma_turn == AdviseX.Top_Turn:
            # # 即使turn down ，但是 ma5 指标不允许 ，不会反转
            # if ka.ma05_trend.ma_turn == AdviseX.Bottom_Turn and ka.ma05_trend.ma_turn_distance == 0 and \
            #         ka.ma05_trend.degree >= 1 and ka.ma05_trend.last_degree_rise >= 1:
            #     result = ""
            # # 指标未超，并下跌压力很大，不会反转
            # if ad.over_points < -12 and (self.indicators.rsi_over.over == False) and \
            #         (self.indicators.kdj_over.over == False) and (self.indicators.cci_over.over == False):
            #     result = ""

            if ka.ma05_trend.last_degree <= 1.5 and ka.ma05_trend.last_degree_rise <= 0.5:
                print("fixed!")
                result = "下跌:确定"

        if (old_stage == "上涨:确定" or old_stage == "上涨:警觉") and add_turn_down and ka.short_degree < 6 and \
                ka.ma05_trend.ma_turn == AdviseX.Top_Turn and ka.ma05_trend.ma_turn_distance == 0:
            print("fixed!")
            result = "下跌:确定"

        if result != "":
            self.stage_fix_expired = Common.after_mins(bar.running_time, 2)
        return result

    @staticmethod
    def confirm_text(confirm):
        return "确定" if confirm else "不确定"

    def get_short_trend(self):
        ka = self.kLineMa
        if ka.short_degree > 0 and ka.ma05_trend.last_degree_rise > 0:
            return AdviseX.Up
        if ka.short_degree < 0 and ka.ma05_trend.last_degree_rise < 0:
            return AdviseX.Down
        return AdviseX.Unknown

    def get_long_trend(self):
        long_degree = self.get_long_degree()
        return AdviseX.Up if long_degree > 0 else AdviseX.Down if long_degree < 0 else AdviseX.Unknown

    def get_long_degree(self):
        ka = self.kLineMa
        if abs(ka.ma30_trend.degree) > 1:
            return ka.ma30_trend.degree

        if abs(ka.ma60_trend.degree) > 1:
            return ka.ma60_trend.degree

        if abs(ka.ma120_trend.degree) > 1:
            return ka.ma120_trend.degree

        if abs(ka.ma240_trend.degree) > 1:
            return ka.ma240_trend.degree

        return 0

    #### buy_open ####

    def buy_open_reason_1(self):
        cr = ConditionResult()
        if self.is_begin_stage:
            pass
            ######################
            # cr.result = self.advise.degree >= 10
            # if cr.result:
            #     cr.reason = f"{self.advise.direction}({self.advise.degree})"
            #     cr.reason_code = "begin:buy_open"
            #     return cr
            ######################
        # else:
        #     cr.result = QA.CROSS(ma.MA5, ma.MA120)[-1] > 0
        #     if cr.result:
        #         cr.reason = f"1、MA5_120 金叉！{bar.close}"
        #         cr.reason_code = "jx:buy_open"

        return cr

    # 失败后处理：因为buy_open后反预期sell_close了，现在预期出现死叉。
    # 如果出现ma线收缩死叉，说明预期正确，取消标记
    # 如果出现ma长线拉升（10分钟内递增）， 说明ma线扩张拉开，证明预期错误，找时机重新buy_open。
    # def buy_open_reason_2(self, bar, ma, last_ma):
    #     cr = ConditionResult()
    #     if self.is_begin_stage:
    #         return cr
    #
    #     # if not self.sell_close_fault:
    #     #     return cr
    #
    #     # 出现ma线收缩死叉，说明预期正确，取消标记
    #     # cross = self.cross_ma.CROSS(ma.MA120, ma.MA5)[-1] > 0
    #     # if cross:
    #     #     self.sell_close_fault = False
    #     #     print(f'{self.running_time} 出现死叉，说明预期正确，取消标记(sell_close_fault)')
    #     #     return cr
    #     #
    #     # cr = self.ma_raise_can_open(ma, self.fault_check_length)
    #     # if cr.result:
    #     #     cr.reason = f"2、失误处理后，ma长线拉升（{self.fault_check_length}分钟内递增）"
    #     #     cr.reason_code = "fix:buy_open"
    #     #     # send_order 后  设置 self.sell_close_fault = False
    #     return cr

    def buy_open_condition(self):
        cr = ConditionResult()
        # 顺序不要动，cr1 和 cr2 都要计算
        cr1 = self.buy_open_reason_1()
        # cr2 = self.buy_open_reason_2(bar, ma, last_ma)
        if cr1.result:
            return cr1
        # elif cr2.result:
        #     return cr2
        return cr

    #### sell_close ####
    # def is_ma05_ma10_up(self):
    #     return True if (self.advise.ma05_trend.degree >= self.last_ma05_degree and self.advise.ma10_trend.degree >= self.last_ma10_degree) and \
    #                    (self.advise.ma20_trend.degree >= self.last_ma20_degree and self.advise.ma10_trend.degree >= self.last_ma10_degree) else False
    #
    # def is_ma05_ma10_down(self):
    #     return True if (self.advise.ma05_trend.degree <= self.last_ma05_degree and self.advise.ma10_trend.degree <= self.last_ma10_degree) and \
    #                    (self.advise.ma20_trend.degree <= self.last_ma20_degree and self.advise.ma10_trend.degree <= self.last_ma10_degree) else False

    def sell_close_reason_1(self, bar, ma, last_ma):
        cr = ConditionResult()
        # 在开盘阶段跟随Ma05--向上
        if self.is_begin_stage:
            # if self.check_high_low_detected_protection():
            #     return cr
            ######################
            # #  背离时，降到  <=3
            # 如果本次开的多单，不要再判断了
            if self.buy_open_time == self.running_time:
                return cr

            # has_ma05_diff_top_turn = self.indicators.has_top_turn(ma.MA5[-5:].diff())
            # if has_ma05_diff_top_turn > 0:
            #     self.after_bottom_count = 100
            #     self.after_top_count = -2
            # max_rise = abs(self.market_data.close[-10:].rolling(5).apply(lambda x: x.diff().dropna().sum()).max() / self.market_data.close[-1])
            # # 最近10分钟内大跌后，ma05差到底后，反转
            # if has_ma05_diff_top_turn > 0 and max_rise > 0.0040:
            #     cr.result = True
            #     cr.reason = f"最近10分钟内大涨后，ma05差到顶，反转"
            #     cr.reason_code = "turn:sell_open"
            #     return cr
            #
            # up_shadow_line = (bar.high - bar.close) / (bar.high - bar.low)
            # if round(self.advise.rise, 4) >= 0.0020 and up_shadow_line > 1 / 3:
            #     cr.result = True
            #     cr.reason = f"大涨，有1/3以上的上影线"
            #     cr.reason_code = "turn:sell_open"
            #     return cr
            #
            # if round(self.advise.rise, 4) >= 0.0015:
            #     if self.min_after_count >= self.after_top_count > 0:
            #         cr.result = True
            #         cr.reason = f"顶部后的大涨，跑路,成交量、涨幅都比上次小,可能会有点上影线"
            #         cr.reason_code = "turn:sell_open"
            #         return cr
            #
            # if round(self.advise.rise, 4) >= 0.0010 and has_ma05_diff_top_turn > 0:
            #     if self.max_after_count > self.after_top_count > self.min_after_count:
            #         cr.result = True
            #         cr.reason = f"过顶部{self.after_top_count}分钟，过顶又大涨！"
            #         cr.reason_code = "turn:sell_open"
            #         return cr
            #
            # if round(self.advise.rise_running, 4) >= 0.0010 and (not self.is_ma05_ma10_up()) and \
            #         (self.advise.ma20_trend.degree < -3 or (self.advise.ma10_trend.degree < -1 and self.advise.ma05_trend.degree < -3)):
            #     cr.result = True
            #     cr.reason = f"逆市多单时，有连续大涨，需要换空"
            #     cr.reason_code = "turn:sell_open"
            #     return cr
            #
            # if (self.max_after_count > self.after_top_count >= self.min_after_count or \
            #     self.max_after_count > self.after_bottom_count >= self.min_after_count or \
            #     (self.advise.ma20_trend.degree < -3 or self.advise.ma05_trend.degree < -3)  # 逆市多单
            # ) and self.advise.cant_up and self.advise.over_points < 0 and (not self.is_ma05_ma10_up()):  # 要有开空的条件:
            #     cr.reason = ""
            #     cr.reason += f"过顶部{self.after_top_count}分钟,无上涨能量！" if self.max_after_count > self.after_top_count >= self.min_after_count else ""
            #     cr.reason += f"过底部{self.after_bottom_count}分钟，无上涨能量！" if self.max_after_count > self.after_bottom_count >= self.min_after_count else ""
            #     if bar.close - 1 <= self.last_ma.max() and (self.last_ma.MA5 < self.last_ma.MA10 < self.last_ma.MA20):
            #         cr.reason += "但收盘价在所有均线下方，无下跌空间"
            #     else:
            #         cr.result = True
            #         cr.reason_code = "turn:sell_open"
            #     return cr
            #
            # if self.advise.cant_up and round(self.advise.rise_running, 4) >= 0.0020:
            #     cr.result = True
            #     cr.reason = f"有连续大涨，涨无可涨，需要换空"
            #     cr.reason_code = "turn:sell_open"
            #
            # if round(self.advise.rise_running, 4) >= 0.0020 and 0 <= self.advise.ma05_trend.degree <= 3 and \
            #         0 <= self.advise.ma10_trend.degree <= 2 and 0 <= self.advise.ma20_trend.degree <= 1:
            #     cr.result = True
            #     cr.reason = f"5,10,20线的角度很平，突然连续的大涨 ,反向"
            #     cr.reason_code = "turn:sell_open"
            ######################
            # if self.advise.direction == AdviseX.Down and self.advise.degree >= 10 and\
            #     (self.advise.direction == self.advise.ma05_trend.direction or self.advise.ma05_trend.degree < self.last_ma05_degree):
            #     cr.result = True
            #     cr.reason = f"强烈建议下跌，换手"
            #     cr.reason_code = "turn:sell_open"
            # cr.result = leave_k and self.cross_advise.degree <= 3
            # if cr.result:
            #     cr.reason = f"背离！及时平多单！"
            #     cr.reason_code = "exit:wait"
            #     return cr
            # #                   跌势             买趋于0
            # cr.result = self.cross_advise.degree <= -7 and self.over_window.mean() >= -2
            # if cr.result:
            #     if self.indicators.macd_cross.leave_k:
            #         cr.result = False
            #         print(f"{self.running_time} 趋势反转！但macd与K线背离，等待转变...")
            #     else:
            #         cr.reason = f"趋势反转！立即平多单！"
            #         cr.reason_code = "turn:sell_open"
            #         return cr
            ######################
            # last_ma05 = ma.MA5[-1:][0]
            # # 使目标价越来越低
            # self.update_sell_close_target(self.buy_open_price + (last_ma05 - self.buy_open_price) / 2, "上升中")
            # # 现价可能小于持仓价，已经赌失败了，下一单sell_open
            # if last_ma05 < self.sell_close_target:
            #     cr.result = True
            #     cr.reason = f"在开盘阶段，MA5{last_ma05}小于目标价{self.sell_close_target}"
            #     cr.reason_code = "turn:sell_open"  #
            # 出现反弹后又向上调头的情况，即一段时间以来，最后一个数字向上，第二个是反弹最低点
            # 此时更新开仓价为反弹最低点价格，不用更新目标价————下轮计算自然会更新过去
            # else:
            #
            #     is_low_point, ma05_min = self.find_low_point(ma)
            #     if is_low_point:
            #         self.update_buy_open_price(ma05_min, "回落底点")
            #     else:
            #         # 找到后，更新变量。万一反转下单，拿这个当持仓价比较准
            #         is_high_point, ma05_max = self.find_high_point(ma)
            #         if is_high_point:
            #             self.last_high_point = ma05_max
            #             print(f'{self.running_time} 最近一个高点：{self.last_high_point}')
            return cr

        # else:
        #     if self.cross_ma.CROSS(ma.MA120, ma.MA5)[-1] > 0:
        #         cr.result = True
        #         cr.reason = f"死叉"
        #         cr.reason_code = "sx:sell_close"
        #     # 可能由于上面的死叉发生保护期间，这里继续检查是否在保护期间已经发生死叉
        #     elif ma.MA5[-1] < ma.MA120[-1]:
        #         cr.result = True
        #         cr.reason = f"在保护期间已经发生死叉"
        #         cr.reason_code = "sx:sell_close"
        #     else:
        #         ma120 = ma.MA120[self.buy_open_time:]
        #         if (ma120[0] >= ma120[1:]).all() and (self.indicators.rsi.RSI1[-1] > BULLS.S - self.fault_close_points):  # 比最高低10点
        #             cr.result = True
        #             cr.reason = f"金叉后，预判ma120应上升，但是ma120继续下降，需要找个高点，RSI1<{BULLS.S - self.fault_close_points}时平仓"
        #             cr.reason_code = "fault:sell_close"

        return cr

    # def find_low_point(self, ma):
    #     # 取长度为5
    #     ma05_length = 5
    #     min_index = ma05_length - 2
    #     ma05_series = ma.MA5[-ma05_length:]
    #     b1 = b2 = b3 = False
    #     # 最大值在倒数第二位置
    #     b1 = ma05_series.argmin() == min_index
    #     ma05_min = ma05_series[min_index]
    #     if b1:
    #         #          最后一个 大于 最小值
    #         b2 = ma05_series[-1] > ma05_min  # 即last_ma05`````
    #     if b2:
    #         #      最小值前面所有的值      大于   最小值
    #         b3 = (ma05_series[:min_index] > ma05_min).all()
    #     # 三者都满足，此时更新开仓价为反弹最高点价格
    #     if b3:
    #         # self.last_high_low_detected = self.running_time
    #         return True, ma05_min
    #
    #     return False, None

    def sell_close_condition(self):
        cr = ConditionResult()
        if not cr.result:
            cr = self.sell_close_reason_1()
        return cr

    #### sell_open ####
    def sell_open_reason_1(self):
        cr = ConditionResult()
        if self.is_begin_stage:
            pass
            ######################
            # cr.result = self.advise.degree <= -10
            # if cr.result:
            #     cr.reason = f"{self.advise.direction}({self.advise.degree})"
            #     cr.reason_code = "begin:sell_open"
            #     return cr
            ######################
        # else:
        #     cr.result = self.cross_ma.CROSS(ma.MA120, ma.MA5)[-1] > 0
        #     if cr.result:
        #         cr.reason = f"1、MA120_5 死叉！"
        #         cr.reason_code = "sx:sell_open"

        return cr

        # 失败后处理：因为sell_open后反预期buy_close了，现在预期出现金叉。
        # 如果出现ma线收缩金叉，说明预期正确，取消标记
        # 如果出现ma长线降低（10分钟内递减）， 说明ma线扩张拉开，证明预期错误，找时机重新sell_open。

    # def sell_open_reason_2(self, bar, ma, last_ma):
    #     cr = ConditionResult()
    #     if self.is_begin_stage:
    #         return cr
    #
    #     # if not self.buy_close_fault:
    #     #     return cr
    #
    #     # 出现金叉，说明预期正确，取消标记
    #     # cross = self.cross_ma.CROSS(ma.MA5, ma.MA120)[-1] > 0
    #     # if cross:
    #     #     self.buy_close_fault = False
    #     #     print(f'{self.running_time} 出现金叉，说明预期正确，取消标记(buy_close_fault)')
    #     #     return cr
    #     #
    #     # cr = self.ma_drop_can_open(ma, self.fault_check_length)
    #     # if cr.result:
    #     #     cr.reason = f"2、失误处理后，ma长线降低（{self.fault_check_length}分钟内递减）"
    #     #
    #     #     cr.reason_code = "fix:sell_open"
    #     #     # send_order 后  设置 self.buy_close_fault = False
    #     return cr

    def sell_open_condition(self):
        cr = ConditionResult()
        cr1 = self.sell_open_reason_1()
        if cr1.result:
            return cr1
        return cr

    # def update_sell_close_target(self, test_price, remark):
    #     if self.sell_close_target < test_price:
    #         self.sell_close_target = test_price
    #         print(f'{self.running_time} 已更新sell_close目标价({remark})：{self.sell_close_target}')
    #
    # def update_buy_open_price(self, test_price, remark):
    #     if self.buy_open_price < test_price:
    #         self.buy_open_price = test_price
    #         print(f'{self.running_time} 已更新buy_open持仓价({remark})：{self.buy_open_price}')

    # def update_buy_close_target(self, test_price, remark):
    #     if self.buy_close_target > test_price:
    #         self.buy_close_target = test_price
    #         print(f'{self.running_time} 已更新buy_close目标价({remark})：{self.buy_close_target}')
    #
    # def update_sell_open_price(self, test_price, remark):
    #     if self.sell_open_price > test_price:
    #         self.sell_open_price = test_price
    #         print(f'{self.running_time} 已更新sell_open持仓价({remark})：{self.sell_open_price}')

    def get_after_count(self):
        ma10_degree = abs(self.advise.ma10_trend.degree)
        return 8 if ma10_degree < 4 else 8 if ma10_degree > 7 else 8, \
               1 if ma10_degree < 4 else 2 if ma10_degree > 7 else 3
        #### buy_close ####

    def buy_close_reason_1(self):
        cr = ConditionResult()
        # 在开盘阶段跟随Ma05--向下
        if self.is_begin_stage:
            # if self.check_high_low_detected_protection():
            #     return cr
            ######################
            # 背离时，降到  >=-3
            # leave_k = Indicators.get_trend(ma.MA120).direction != AdviseX.Down or Indicators.get_trend(ma.MA60).direction != AdviseX.Down
            # 如果本次开的空单，不要再判断了
            if self.sell_open_time == self.running_time:
                return cr

            # has_ma05_diff_bottom_turn = self.indicators.has_bottom_turn(ma.MA5[-5:].diff())
            # if has_ma05_diff_bottom_turn > 0:
            #     self.after_bottom_count = -2
            #     self.after_top_count = 100
            # max_rise = abs(self.market_data.close[-10:].rolling(5).apply(lambda x: x.diff().dropna().sum()).min() / self.market_data.close[-1])
            # # 最近10分钟内大跌后，ma05差到底后，反转
            # if has_ma05_diff_bottom_turn > 0 and max_rise > 0.0040:
            #     cr.result = True
            #     cr.reason = f"最近10分钟内大跌后，ma05差到底，反转"
            #     cr.reason_code = "turn:buy_open"
            #     return cr
            #
            # low_shadow_line = (bar.close - bar.low) / (bar.high - bar.low)
            # if round(self.advise.rise, 4) <= -0.0020 and low_shadow_line > 1 / 3:
            #     cr.result = True
            #     cr.reason = f"大跌，有1/3以上的下影线"
            #     cr.reason_code = "turn:buy_open"
            #     return cr
            #
            # if round(self.advise.rise, 4) <= -0.0015:
            #     if self.min_after_count >= self.after_bottom_count > 0:
            #         cr.result = True
            #         cr.reason = f"底部后的大跌，跑路，待验证：成交量、涨幅都比上次小,可能会有点下影线"
            #         cr.reason_code = "turn:buy_open"
            #         return cr
            #
            # if round(self.advise.rise, 4) <= -0.0010 and has_ma05_diff_bottom_turn > 0:
            #     if self.max_after_count > self.after_bottom_count > self.min_after_count:
            #         cr.result = True
            #         cr.reason = f"过底部{self.after_bottom_count}分钟，过底又大跌！"
            #         cr.reason_code = "turn:buy_open"
            #         return cr
            #
            # if round(self.advise.rise_running, 4) <= -0.0010 and (not self.is_ma05_ma10_down()) and \
            #         (self.advise.ma20_trend.degree > 3 or (self.advise.ma10_trend.degree > 1 and self.advise.ma05_trend.degree > 3)):
            #     cr.result = True
            #     cr.reason = f"逆市空单时，有连续大跌，需要换多"
            #     cr.reason_code = "turn:buy_open"
            #     return cr
            #
            # if (self.max_after_count > self.after_top_count > self.min_after_count or \
            #     self.max_after_count > self.after_bottom_count > self.min_after_count or \
            #     (self.advise.ma20_trend.degree > 3 or self.advise.ma05_trend.degree > 3)  # 逆市空单
            # ) and self.advise.cant_down and self.advise.over_points > 0 and (not self.is_ma05_ma10_down()):  # 要有开多的条件
            #     cr.reason = ""
            #     cr.reason += f"过顶部{self.after_top_count}分钟,无下跌能量！" if self.max_after_count > self.after_top_count > self.min_after_count else ""
            #     cr.reason += f"过底部{self.after_bottom_count}分钟，无下跌能量！" if self.max_after_count > self.after_bottom_count > self.min_after_count else ""
            #     if bar.close + 1 >= self.last_ma.max() and (self.last_ma.MA5 > self.last_ma.MA10 > self.last_ma.MA20):
            #         cr.reason += "但收盘价在所有均线上方，无上涨空间"
            #     else:
            #         cr.result = True
            #         cr.reason_code = "turn:buy_open"
            #     return cr
            #
            # if self.advise.cant_down and round(self.advise.rise_running, 4) <= -0.0020:
            #     cr.result = True
            #     cr.reason = f"有连续大跌，跌无可跌，需要换多"
            #     cr.reason_code = "turn:buy_open"
            #
            # # 5,10,20线的角度很平，突然连续的大涨大跌 ,反向
            # if round(self.advise.rise_running, 4) <= -0.0020 and 0 >= self.advise.ma05_trend.degree >= -3 and \
            #         0 >= self.advise.ma10_trend.degree >= -2 and 0 >= self.advise.ma20_trend.degree >= -1:
            #     cr.result = True
            #     cr.reason = f"5,10,20线的角度很平，突然连续的大跌 ,反向"
            #     cr.reason_code = "turn:buy_open"
            ######################
            # if self.advise.direction == AdviseX.Up and self.advise.degree >= 10 and \
            #         (self.advise.direction == self.advise.ma05_trend.direction or self.advise.ma05_trend.degree > self.last_ma05_degree):
            #     cr.result = True
            #     cr.reason = f"强烈建议上涨，换手"
            #     cr.reason_code = "turn:buy_open"
            # cr.result = leave_k and self.cross_advise.degree >= -3
            # if cr.result:
            #     cr.reason = f"背离！及时平空单！"
            #     cr.reason_code = "exit:wait"
            #     return cr
            # #                   涨势
            # cr.result = self.cross_window.values[-1] >= 7 and self.over_window.mean() <= 2
            # if cr.result:
            #     if self.indicators.macd_cross.leave_k:
            #         cr.result = False
            #         print(f"{self.running_time} 趋势反转！但macd与K线背离，等待转变...")
            #     else:
            #         cr.reason = f"趋势反转！立即平空单！"
            #         cr.reason_code = "turn:buy_open"
            #         return cr
            ######################
            # MA5没有调头的话，就拉低平仓目标价位：开仓价 - （开仓价-现价）/2,即拿出一半的收益赌
            # 如果达到就平仓；如果没达到之前，出现向下调头的情况，把刚才的高点作为开仓价。

            # last_ma05 = ma.MA5[-1:][0]
            # # 使目标价越来越低
            # self.update_buy_close_target(self.sell_open_price - (self.sell_open_price - last_ma05) / 2, "下降中")
            # # 现价可能大于于持仓价，已经赌失败了，下一单buy_open
            # if last_ma05 > self.buy_close_target:
            #     cr.result = True
            #     cr.reason = f"在开盘阶段，MA5{last_ma05}大于目标价{self.buy_close_target}"
            #     cr.reason_code = "turn:buy_open"
            # 出现反弹后又向下调头的情况，即一段时间以来，最后一个数字向下，第二个是反弹最高点
            # 此时更新开仓价为反弹最高点价格，不用更新目标价————下轮计算自然会更新过去
            # else:
            #     is_high_point, ma05_max = self.find_high_point(ma)
            #     if is_high_point:
            #         self.update_sell_open_price(ma05_max, "反弹顶点")
            #     else:
            #         # 找到后，更新变量。万一反转下单，拿这个当持仓价比较准
            #         is_low_point, ma05_min = self.find_low_point(ma)
            #         if is_low_point:
            #             self.last_low_point = ma05_min
            #             print(f'{self.running_time} 最近一个低点：{self.last_low_point}')

            return cr

        # else:
        #     # 等待金叉
        #     if self.cross_ma.CROSS(ma.MA5, ma.MA120)[-1] > 0:
        #         cr.result = True
        #         cr.reason = f"金叉"
        #         cr.reason_code = "jx:buy_close"
        #         # 可能由于上面的死叉发生保护期间，这里继续检查是否在保护期间已经发生死叉
        #     elif ma.MA5[-1] > ma.MA120[-1]:
        #         cr.result = True
        #         cr.reason = f"在保护期间已经发生金叉"
        #         cr.reason_code = "sx:buy_close"
        #     else:
        #         ma120 = ma.MA120[self.sell_open_time:]
        #         if (ma120[0] <= ma120[1:]).all() and (self.indicators.rsi.RSI1[-1] < BEARS.S + self.fault_close_points):  # 比最低高10点
        #             cr.result = True
        #             cr.reason = f"死叉后，预判ma120应下降，但是ma120继续上升，需要找个低点，RSI1<{BEARS.S + self.fault_close_points}时平仓"
        #             cr.reason_code = "fault:buy_close"

        return cr

    # def find_high_point(self, ma):
    #     # 取长度为5
    #     ma05_length = 5
    #     max_index = ma05_length - 2
    #     ma05_series = ma.MA5[-ma05_length:]
    #     b1 = b2 = b3 = False
    #     # 最大值在倒数第二位置
    #     b1 = ma05_series.argmax() == max_index
    #     ma05_max = ma05_series[max_index]
    #     if b1:
    #         #          最后一个 小于 最大值
    #         b2 = ma05_series[-1] < ma05_max  # 即last_ma05
    #     if b2:
    #         #      最大值前面所有的值      小于   最大值
    #         b3 = (ma05_series[:max_index] < ma05_max).all()
    #     # 三者都满足，此时更新开仓价为反弹最高点价格
    #     if b3:
    #         # self.last_high_low_detected = self.running_time
    #         return True, ma05_max
    #
    #     return False, None

    def buy_close_condition(self):
        cr = ConditionResult()
        if not cr.result:
            cr = self.buy_close_reason_1()
        return cr

    # def ma_drop_can_open(self, ma, check_length):
    #     cr = ConditionResult()
    #     ma_drop_down = (ma.MA120[-(check_length + 1):].diff().dropna() < 0).all()
    #     cr.result = ma_drop_down and \
    #                 (True if rsi is None else self.indicators.rsi.RSI1[-1] > BULLS.S - self.fault_open_points)
    #     return cr
    #
    # def ma_raise_can_open(self, ma, check_length):
    #     cr = ConditionResult()
    #     ma_raise_up = (ma.MA120[-(check_length + 1):].diff().dropna() > 0).all()
    #     cr.result = ma_raise_up and \
    #                 (True if rsi is None else self.indicators.rsi.RSI1[-1] < BEARS.S + self.fault_open_points)
    #     return cr

    def my_force_close(self, bar):
        cr = ConditionResult(True, reason=f"原因：时间太晚了！强平！", reason_code="force:go_home")
        if self.positions.volume_long > 0:
            self.sell_close(bar, cr)

        if self.positions.volume_short > 0:
            self.buy_close(bar, cr)

    ##### SEND ORDER #####
    def buy_close(self, bar, condition):
        # if self.check_open_protection():
        #     return

        self.send_order('BUY', 'CLOSE', price=bar.close, volume=self.positions.volume_short)
        if condition.reason_code == "fault:buy_close":
            self.tag_fault_flag(condition.reason_code)
        print(f'{self.running_time} {condition.reason}')
        profit = self.positions.position_price_short - bar.close
        profit_text = '盈利' if profit > 0 else "亏损"
        print(f'{self.running_time} ***平空单:现价={bar.close} \
                持空单价{self.positions.position_price_short} {profit_text}={abs(profit)}')

        # self.check_begin_stage()

        if self.is_begin_stage and condition.reason_code == "turn:buy_open":
            cr = ConditionResult(True, reason="反转操作：接续turn:buy_close=>turn:buy_open", reason_code="turn:sell_open")
            self.buy_open(bar, cr)

    def buy_open(self, bar, condition):
        # if self.check_open_protection():
        #     return

        self.send_order('BUY', 'OPEN', price=bar.close, volume=1)
        # if condition.reason_code == "fix:buy_open":
        #     self.sell_close_fault = False
        print(f'{self.running_time} {condition.reason}')
        print(f'{self.running_time} ***开多单={self.positions.position_price_long}')
        self.buy_open_time = self.running_time
        # 根据MA5计算：如有下单前的低点，则取此低点，否则取当前MA05
        # self.buy_open_time = self.running_time
        # self.buy_open_price = self.sell_close_target = self.last_low_point \
        #     if self.last_low_point is not None else self.last_ma["MA5"]
        # print(f"{self.running_time} buy_open_price/sell_close_target 设置为：{self.buy_open_price}")

    def sell_close(self, bar, condition):
        # if self.check_open_protection():
        #     return

        self.send_order('SELL', 'CLOSE', price=bar.close, volume=self.positions.volume_long)
        if condition.reason_code == "fault:sell_close":
            self.tag_fault_flag(condition.reason_code)
        print(f'{self.running_time} {condition.reason}')
        profit = bar.close - self.positions.position_price_long
        profit_text = '盈利' if profit > 0 else "亏损"
        print(f'{self.running_time} ***平多单:现价={bar.close} \
                持多单价{self.positions.position_price_long} {profit_text}={abs(profit)}')

        # self.check_begin_stage()

        if self.is_begin_stage and condition.reason_code == "turn:sell_open":
            cr = ConditionResult(True, reason="反转操作：接续turn:sell_close=>turn:sell_open", reason_code="turn:buy_open")
            self.sell_open(bar, cr)

    def sell_open(self, bar, condition):
        # if condition.reason_code == "begin:sell_open":
        #     self.ensure_trend = True
        # if self.check_open_protection():
        #     return

        self.send_order('SELL', 'OPEN', price=bar.close, volume=1)
        # if condition.reason_code == "fix:sell_open":
        #     self.buy_close_fault = False
        print(f'{self.running_time} {condition.reason}')
        print(f'{self.running_time} ***开空单={self.positions.position_price_short}')
        self.sell_open_time = self.running_time
        # self.sell_open_time = self.running_time
        # 根据MA5计算 ,如有下单前的高点，则取此高点，否则取当前MA05
        # self.sell_open_price = self.buy_close_target = self.last_high_point \
        #     if self.last_high_point is not None else self.last_ma["MA5"]
        # print(f"{self.running_time} sell_open_price/buy_close_target 设置为：{self.sell_open_price}")

    def tag_fault_flag(self, reason_code):
        code = reason_code.split(":")
        setattr(self, f'{code[0]}_{code[1]}', True)
        setattr(self, f'{code[0]}_{code[1]}_time', self.running_time)

    def risk_check(self):
        pass

    def dual_cross(self, bar):
        return dual_cross_func(self.market_data)

    def fetch_pre_trade_data(self, pre_trade_date):
        pre_data = QA.QA_quotation(self.code.upper(), pre_trade_date + " 09:00:00", pre_trade_date + " 23:00:00",
                                   source=QA.DATASOURCE.MONGO, frequence=self.frequence, market=self.market_type,
                                   output=QA.OUTPUT_FORMAT.DATASTRUCT)
        return pre_data.data[-250:]

    def add_pre_trade_data(self, item):
        self._market_data.append(copy.deepcopy(item))

    def on_dailyopen(self):
        # 每天开始是开盘阶段，趋势未定，此时，跟MA5走
        # 待确定完趋势，开盘阶段设置为否
        # self.ensure_trend = False
        self.is_begin_stage = True
        self.begin_trade_time = None
        print(f'***账户 可用资金={self.acc.cash_available}')

        if len(self._market_data) == 0:
            pre_market_data = self.fetch_pre_trade_data(QA.QA_util_get_pre_trade_date(self.start, 1))
            pre_market_data.apply(self.add_pre_trade_data, axis=1)
            print(f'{self.trade_date} 补充前一交易日的数据{len(self._market_data)}条')

    def on_dailyclose(self):

        # on_dailyclose先于on_dailyopen执行
        # 第一次进来，设置为开始交易就行了
        # 第二次进来执行 变更交易日
        if self.begin_trade:
            next_trade_date = QA.QA_util_get_next_day(self.start, 1)
            print(f'{self.trade_date} 即将进入下一交易日：{next_trade_date}')
            self.trade_date = next_trade_date
        else:
            self.begin_trade = True

    @property
    def market_data(self):
        if self.running_mode == 'sim':
            return self._market_data
        elif self.running_mode == 'backtest':
            return pd.concat(self._market_data, axis=1, sort=False).T

    # def check_high_low_detected_protection(self):
    #     if not self.is_begin_stage:
    #         return False
    #
    #     protection = QA.QA_util_get_next_period(self.last_high_low_detected, self.high_low_detected_protection)
    #     if pd.to_datetime(self.running_time) < protection:
    #         print(f'{self.running_time} 正在高低点检测保护期 {protection} 内')
    #         return True
    #
    #     return False

    # def check_open_protection(self):
    #     if self.is_begin_stage:
    #         return False
    #
    #     if self.buy_open_time is not None:
    #         protection = QA.QA_util_get_next_period(self.buy_open_time, self.open_protection)
    #         if pd.to_datetime(self.running_time) < protection:
    #             print(f'{self.running_time} 正在开仓保护期 {protection} 内')
    #             return True
    #     if self.sell_open_time is not None:
    #         protection = QA.QA_util_get_next_period(self.sell_open_time, self.open_protection)
    #         if pd.to_datetime(self.running_time) < protection:
    #             print(f'{self.running_time} 正在开仓保护期{protection}内')
    #             return True
    #     return False

    def check_begin_stage(self):
        if self.is_begin_stage and self.tt > QA.QA_util_get_next_period(self.begin_trade_time, "D"):
            self.is_begin_stage = False
            print(f'{self.running_time} 开盘阶段结束')

    def in_start_open_time(self):
        return self.market_open_am < self.tt.time() < self.stop_open_am or \
               self.market_open_pm < self.tt.time() < self.stop_open_pm or \
               self.market_open_ni < self.tt.time() < self.stop_open_ni

    def in_stop_open_time(self):
        return self.stop_open_am < self.tt.time() < self.force_close_am or \
               self.stop_open_pm < self.tt.time() < self.force_close_pm or \
               self.stop_open_ni < self.tt.time() < self.force_close_ni

    def in_force_close_time(self):
        return self.force_close_am < self.tt.time() < self.market_close_am or \
               self.force_close_pm < self.tt.time() < self.market_close_pm or \
               self.force_close_ni < self.tt.time() < self.market_close_ni


if __name__ == '__main__':
    strategy = MaStrategy(code='rbl8', frequence='1min', strategy_id='mystrategy12', start='2021-06-22 09:00:00',
                          end='2021-06-22 23:00:00',
                          init_cash=20000, portfolio="cao11")
    strategy.run_backtest()
